#pragma once
#include "Types.h"
#include "EnumFlags.h"

#include <Rz/Utils/Cryptography/OAT.h>

namespace Rz {

class TypeInfo
{
    public:
		RZ_INLINE u32 GetInheritedClassCount() const			{ return _numInherited; }
		RZ_INLINE const TypeInfo* GetInherited(u32 index) const	{ return index < _numInherited ? _inherited[index] : 0; }
		
		RZ_INLINE u32 GetTemplateParametersCount() const		{ return _numTemplateParams; }
		RZ_INLINE const TypeInfo* GetTemplateParameter(u32 index) const { return index < _numTemplateParams ? _templateParams[index] : 0; }
		RZ_INLINE u32 GetTemplateParametersHash() const			{ return _templateParamsHash; }

		RZ_INLINE const char* GetClassName() const				{ return _className; }
		RZ_INLINE u32 GetClassNameHash() const					{ return _classNameHash; }

		RZ_INLINE const char* GetFullClassName() const			{ return _fullClassName; }
		RZ_INLINE u32 GetFullClassNameHash() const				{ return _fullClassNameHash; }

		RZ_INLINE bool IsInterface() const						{ return _isInterface; }

		RZ_INLINE size_t GetSize() const						{ return _size; }

		bool Contains(const TypeInfo* typeInfo) const
		{
			if (!typeInfo)
			{
				return false;
			}

			if ((_fullClassNameHash == typeInfo->GetFullClassNameHash()) &&
				(_templateParamsHash == typeInfo->GetTemplateParametersHash()))
			{
				return true;
			}

			for (u32 i = 0; i < _numInherited; ++i)
			{
				if (_inherited[i]->Contains(typeInfo))
				{
					return true;
				}
			}

			return false;
		}

		bool Implements(const TypeInfo* typeInfo) const
		{
			if (!typeInfo)
			{
				return false;
			}

			if ((_fullClassNameHash == typeInfo->GetFullClassNameHash()) &&
				(_templateParamsHash == typeInfo->GetTemplateParametersHash()))
			{
				return true;
			}

			for (u32 i = 0; i < _numInherited; ++i)
			{
				if (_inherited[i]->Implements(typeInfo))
				{
					return true;
				}
			}

			return false;
		}

		RZ_INLINE bool IsSameAs(const TypeInfo* typeInfo) const
		{
			if (!typeInfo)
			{
				return false;
			}

			return ((_fullClassNameHash == typeInfo->GetFullClassNameHash()) &&
					(_templateParamsHash == typeInfo->GetTemplateParametersHash()));
		}

	public:
		RZ_INLINE TypeInfo(bool isInterface, const char* fullClassName, const char* className, size_t size)
			: _fullClassName(fullClassName)
			, _className(className)
			, _fullClassNameHash(Utils::Cryptography::OAT::HashString(fullClassName))
			, _classNameHash(Utils::Cryptography::OAT::HashString(className))
			, _numTemplateParams(0)
			, _templateParamsHash(0)
			, _isInterface(isInterface)
			, _lockFlags(LockFlag::Unlocked)
			, _size(size)
		{

		}
		
		void Inherit(const TypeInfo* typeInfo)
		{
			if (typeInfo)
			{
				_inherited[_numInherited] = typeInfo;
				++_numInherited;
			}
		}

		template <typename _empty>
		RZ_INLINE void Inherit()
		{

		}

		template <typename _empty, typename T, typename ...Args>
		RZ_INLINE void Inherit()
		{
			this->FirstInherit<Args...>();
			this->Inherit(T::GetTypeInfoStatic());
			this->Inherit<void, Args...>();
		}

		void TemplateParameter(const TypeInfo* typeInfo)
		{
			if (typeInfo)
			{
 				_templateParams[_numTemplateParams] = typeInfo;
				++_numTemplateParams;
			}
		}

		template <typename _empty>
		RZ_INLINE void TemplateParameter()
		{
			_templateParamsHash = Utils::Cryptography::OAT::HashData(_templateParams, sizeof(TypeInfo*) * _numTemplateParams);
		}

		template <typename _empty, typename T, typename ...Args>
		RZ_INLINE void TemplateParameter()
		{
			this->FirstTemplateParameter<Args...>();
			this->TemplateParameter(T::GetTypeInfoStatic());
			this->TemplateParameter<void, Args...>();
		}

		RZ_INLINE bool IsLocked() const { return ((_lockFlags & LockFlag::ClassLocked) == LockFlag::ClassLocked); }
		RZ_INLINE void Lock() { _lockFlags |= LockFlag::ClassLocked; }

	private:
		enum LockFlag
		{
			Unlocked			= RZ_DEFINE_ZERO_ENUMFLAG,
			ClassLocked			= RZ_DEFINE_ENUMFLAG(0),
			InheritLocked		= RZ_DEFINE_ENUMFLAG(1),
			TemplatesLocked		= RZ_DEFINE_ENUMFLAG(2)
		};

		RZ_MARK_AS_TYPED_ENUMFLAGS_FRIEND(LockFlag, u8);

		template <LockFlag TLockFlag>
		bool Process()
		{
			if (!(_lockFlags & TLockFlag))
			{
				_lockFlags |= TLockFlag;
				return true;
			}
			else
			{
				return false;
			}
		}

		template <typename ...Args>
		void FirstInherit()
		{
			if (Process<LockFlag::InheritLocked>())
			{
				static TypeInfo* inherited[sizeof...(Args) + 1];
				_inherited = const_cast<const TypeInfo**>(inherited);
			}
		}

		template <typename ...Args>
		void FirstTemplateParameter()
		{
			if (Process<LockFlag::TemplatesLocked>())
			{
				static TypeInfo* templateParams[sizeof...(Args) + 1];
				_templateParams = const_cast<const TypeInfo**>(templateParams);
			}
		}

		bool _isInterface;

		size_t _size;

		const char* _className;
		Utils::Cryptography::Hash _classNameHash;

		const char* _fullClassName;
		Utils::Cryptography::Hash _fullClassNameHash;

		const TypeInfo** _inherited;
		u32 _numInherited;

		const TypeInfo** _templateParams;
		u32 _numTemplateParams;
		Utils::Cryptography::Hash _templateParamsHash;

		LockFlag _lockFlags;
};

//////////////////////////////////////////////////////////////////////////

template <typename T>
struct NonObjectType
{
	static const TypeInfo* GetTypeInfoStatic()
	{
		static TypeInfo typeInfo = TypeInfo(false, "<undefined>", "<undefined>", 0);
		typeInfo.Lock();
		return &typeInfo;
	}

	virtual const TypeInfo* GetTypeInfo() const
	{
		return GetTypeInfoStatic();
	}
};

//////////////////////////////////////////////////////////////////////////
#define _RZ_RTTI_BEGIN_CODE(i, ns, cn)								\
	public:															\
		RZ_AS_METHOD(cn);											\
																	\
		RZ_INLINE static const ::Rz::TypeInfo* GetTypeInfoStatic()	\
		{															\
			static ::Rz::TypeInfo typeInfo = ::Rz::TypeInfo(i, RZ_STRINGIFY(ns) "::" RZ_STRINGIFY(cn), RZ_STRINGIFY(cn), sizeof(cn));\
			if (!typeInfo.IsLocked())								\
			{														\
				InitRtti(&typeInfo, nullptr);						\
			}														\
			typeInfo.Lock();										\
			return &typeInfo;										\
		}															\
																	\
		RZ_INLINE virtual const ::Rz::TypeInfo* GetTypeInfo() const override\
		{															\
			return GetTypeInfoStatic();								\
		}															\
																	\
	protected:														\
		virtual void RegisterAllSubclasses() const override			\
		{															\
			RegisterThisClass();									\
			InitRtti(nullptr, this);								\
		}															\
																	\
		void RegisterThisClass() const								\
		{															\
			RegisterSubclass(static_cast<const void*>(this), cn::GetTypeInfo()->GetFullClassNameHash());\
		}															\
																	\
		template <typename _empty>									\
		void RegisterAllSubclasses() const							\
		{															\
																	\
		}															\
																	\
		template <typename _empty, typename T, typename... Args>	\
		void RegisterAllSubclasses() const							\
		{															\
			T::RegisterAllSubclasses();								\
			RegisterAllSubclasses<void, Args...>();					\
		}															\
																	\
		static void InitRtti(TypeInfo* typeInfo, const cn* classPtr)\
		{															\

#define _RZ_RTTI_END_CODE				} public:

//////////////////////////////////////////////////////////////////////////

#define _RZ_RTTI_NON_OBJECT_BODY(cn, fcn, i, sfcn)			\
	template <>												\
	struct NonObjectType<fcn>								\
	{														\
		static const ::Rz::TypeInfo* GetTypeInfoStatic()	\
		{													\
			static ::Rz::TypeInfo typeInfo = ::Rz::TypeInfo(i, sfcn, RZ_STRINGIFY(cn), sizeof(cn)); \
			typeInfo.Lock();								\
			return &typeInfo;								\
		}													\
		virtual const ::Rz::TypeInfo* GetTypeInfo() const { return GetTypeInfoStatic(); }	\
	}


#define RZ_RTTI_DECLARE_NON_OBJECT(ns, cn)					_RZ_RTTI_NON_OBJECT_BODY(cn, ns::cn, false, RZ_STRINGIFY(ns) "::" RZ_STRINGIFY(cn))
#define RZ_RTTI_DECLARE_NON_OBJECT_INTERFACE(ns, cn)		_RZ_RTTI_NON_OBJECT_BODY(cn, ns::cn, true,  RZ_STRINGIFY(ns) "::" RZ_STRINGIFY(cn))
#define RZ_RTTI_DECLARE_NON_OBJECT_AT_ROOT(cn)				_RZ_RTTI_NON_OBJECT_BODY(cn,   ::cn, false,                  "::" RZ_STRINGIFY(cn))
#define RZ_RTTI_DECLARE_NON_OBJECT_INTERFACE_AT_ROOT(cn)	_RZ_RTTI_NON_OBJECT_BODY(cn,   ::cn, true,                   "::" RZ_STRINGIFY(cn))
#define RZ_RTTI_DECLARE_NON_OBJECT_STD_TYPE(cn)				_RZ_RTTI_NON_OBJECT_BODY(cn,     cn, false,                       RZ_STRINGIFY(cn))

RZ_RTTI_DECLARE_NON_OBJECT(Rz, u8);
RZ_RTTI_DECLARE_NON_OBJECT(Rz, u16);
RZ_RTTI_DECLARE_NON_OBJECT(Rz, u32);
RZ_RTTI_DECLARE_NON_OBJECT(Rz, u64);
RZ_RTTI_DECLARE_NON_OBJECT(Rz, s8);
RZ_RTTI_DECLARE_NON_OBJECT(Rz, s16);
RZ_RTTI_DECLARE_NON_OBJECT(Rz, s32);
RZ_RTTI_DECLARE_NON_OBJECT(Rz, s64);
RZ_RTTI_DECLARE_NON_OBJECT(Rz, f32);
RZ_RTTI_DECLARE_NON_OBJECT(Rz, f64);

RZ_RTTI_DECLARE_NON_OBJECT(Rz, u8*);
RZ_RTTI_DECLARE_NON_OBJECT(Rz, u16*);
RZ_RTTI_DECLARE_NON_OBJECT(Rz, u32*);
RZ_RTTI_DECLARE_NON_OBJECT(Rz, u64*);
RZ_RTTI_DECLARE_NON_OBJECT(Rz, s8*);
RZ_RTTI_DECLARE_NON_OBJECT(Rz, s16*);
RZ_RTTI_DECLARE_NON_OBJECT(Rz, s32*);
RZ_RTTI_DECLARE_NON_OBJECT(Rz, s64*);
RZ_RTTI_DECLARE_NON_OBJECT(Rz, f32*);
RZ_RTTI_DECLARE_NON_OBJECT(Rz, f64*);

RZ_RTTI_DECLARE_NON_OBJECT_STD_TYPE(void*);

}

//////////////////////////////////////////////////////////////////////////

// Interfaces needs virtual destructor for destroying derived objects properly
#define RZ_MARK_AS_INTERFACE(type)	\
	virtual ~type() { }				\
	template <typename _TTVV, typename... Args> RZ_INLINE static _TTVV* CreateInstance(Args... params) { return new _TTVV(params...); }


#ifdef RZ_INCLUDE_RTTI
#	define RZ_RTTI(ns, cn)										_RZ_RTTI_BEGIN_CODE(false, ns, cn)
#	define RZ_RTTI_INTERFACE(ns, cn)							RZ_MARK_AS_INTERFACE(cn); _RZ_RTTI_BEGIN_CODE(true,  ns, cn)

#	define RZ_RTTI_TEMPLATES(...)								if (typeInfo != nullptr) { typeInfo->TemplateParameter<void, __VA_ARGS__>();	 }
#	define RZ_RTTI_IMPLEMENTS(...)								if (typeInfo != nullptr) { typeInfo->Inherit<void, __VA_ARGS__>();				 }\
																if (classPtr != nullptr) { classPtr->RegisterAllSubclasses<void, __VA_ARGS__>(); }
#	define RZ_RTTI_END											_RZ_RTTI_END_CODE
#else
#	define RZ_RTTI(ns, cn)
#	define RZ_RTTI_INTERFACE(ns, cn)							RZ_MARK_AS_INTERFACE(cn);

#	define RZ_RTTI_TEMPLATES(...)
#	define RZ_RTTI_IMPLEMENTS(...)
#	define RZ_RTTI_END
#endif
